Engee 文档
Notebook

预测医疗费用的回归任务

导言

在此示例中,将执行探索性数据分析,包括对集合结构的初始检查,研究特征和目标变量的分布,识别遗漏,异常值和重复项,以及分析变量之间的相关性。 在下一阶段,将构建和训练回归模型,以便预测目标变量并使用适当的指标评估其工作质量。 此外,将对特征的重要性进行分析,这将确定哪些特征对模型的预测贡献最大,并且可以被认为对所研究的任务最重要。

必须安装库

您必须手动指定项目所在的路径才能转到工作目录,以及安装必要的依赖项。

在开始工作之前,我们将导入所有必要的库。

In [ ]:
 !pip install -r /user/Demo_public/biomedical/predictionmeddata/requirements.txt
In [ ]:
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
from scipy.stats import randint, uniform, loguniform
from sklearn.model_selection import train_test_split, RandomizedSearchCV, KFold
from sklearn.linear_model import Lasso
from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score
from sklearn.ensemble import RandomForestRegressor
from catboost import CatBoostRegressor

我们数数据,看看它们是什么。

In [ ]:
df = pd.read_csv("insurance.csv")
df.head(10)
Out[0]:
age sex bmi children smoker region charges
0 19 female 27.900 0 yes southwest 16884.92400
1 18 male 33.770 1 no southeast 1725.55230
2 28 male 33.000 3 no southeast 4449.46200
3 33 male 22.705 0 no northwest 21984.47061
4 32 male 28.880 0 no northwest 3866.85520
5 31 female 25.740 0 no southeast 3756.62160
6 46 female 33.440 1 no southeast 8240.58960
7 37 female 27.740 3 no northwest 7281.50560
8 37 male 29.830 2 no northeast 6406.41070
9 60 female 25.840 0 no northwest 28923.13692

让我们分析一下表:

  1. 年龄-年龄
  2. 性别-性别
  3. BMI-体重指数
  4. 儿童—儿童人数
  5. 吸烟者-吸烟与否
  6. 地区-居住地区
  7. 费用-医疗费用

基于此表,我们有特征1到6-回归模型的输入数据,特征7-需要根据特征1-6预测的目标。

EDA分析

让我们检查表中的差距

In [ ]:
df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 1338 entries, 0 to 1337
Data columns (total 7 columns):
 #   Column    Non-Null Count  Dtype  
---  ------    --------------  -----  
 0   age       1338 non-null   int64  
 1   sex       1338 non-null   object 
 2   bmi       1338 non-null   float64
 3   children  1338 non-null   int64  
 4   smoker    1338 non-null   object 
 5   region    1338 non-null   object 
 6   charges   1338 non-null   float64
dtypes: float64(2), int64(2), object(3)
memory usage: 73.3+ KB

没有通行证。 我们在表3中看到稍后将编码用于训练模型的分类特征,其余特征是数字特征。 你也可以看到没有差距。

In [ ]:
df.describe()
Out[0]:
age bmi children charges
count 1338.000000 1338.000000 1338.000000 1338.000000
mean 39.207025 30.663397 1.094918 13270.422265
std 14.049960 6.098187 1.205493 12110.011237
min 18.000000 15.960000 0.000000 1121.873900
25% 27.000000 26.296250 0.000000 4740.287150
50% 39.000000 30.400000 1.000000 9382.033000
75% 51.000000 34.693750 2.000000 16639.912515
max 64.000000 53.130000 5.000000 63770.428010

统计数据显示,人们的平均年龄为39岁,偏差为14,即从25到53不等。 还可以看出,bmi=53存在-这是最大值,大大超过临界阈值。 也许这是一个异常值。 还可以看出,一半的人的bmi超过30,这意味着大多数人患有肥胖的初始阶段,四分之一的人患有最后程度的肥胖。 最大的成本是63k常规单位(CU),这是非常出图(均值+3sigma,均值-3sigma),也许这个观察也是一个异常值

经典机器学习模型不能使用分类特征,即不是数字的特征。 它们需要编码成数字。 我们使用一个热编码技术

In [ ]:
df_encode = pd.get_dummies(df, columns=["sex", "region", "smoker"])
df_encode = df_encode.drop('sex_female', axis=1)
df_encode = df_encode.drop('smoker_no', axis=1)
df_encode.head()
Out[0]:
age bmi children charges sex_male region_northeast region_northwest region_southeast region_southwest smoker_yes
0 19 27.900 0 16884.92400 False False False False True True
1 18 33.770 1 1725.55230 True False False True False False
2 28 33.000 3 4449.46200 True False False True False False
3 33 22.705 0 21984.47061 True False True False False False
4 32 28.880 0 3866.85520 True False True False False False

首先,让我们研究数据的分布。 让我们为标志建立直方图

In [ ]:
colors = ['blue', 'green', 'red', 'purple', 'orange']
fig, axes = plt.subplots(nrows=4, ncols=1,figsize=(7, 7))
axes = axes.flatten()
axes[0].hist(df["age"], bins=10, color=colors[0])
axes[0].set_title('年龄')
axes[0].set_xlabel("人的年龄")
axes[0].set_ylabel("人数")

axes[1].hist(df["bmi"], bins=10, color=colors[0])
axes[1].set_title('bmi')
axes[1].set_xlabel("bmi")
axes[1].set_ylabel("人数")

axes[2].hist(df["children"], bins=6, color=colors[0])
axes[2].set_title('儿童')
axes[2].set_xlabel("儿童人数")
axes[2].set_ylabel("人数")

axes[3].hist(df["charges"], bins=10, color=colors[0])
axes[3].set_title('开支')
axes[3].set_xlabel("人民开支")
axes[3].set_ylabel("人数")
plt.subplots_adjust(hspace=0.6)
plt.tight_layout()

通过年龄,可以看出样本中的数据分布相当均匀:既有年轻(从18岁开始),也有老年人(高达64岁),但没有明显的扭曲。

BMI分布具有正态分布的外观,就像向右移动的钟形。 大多数人在25到35的范围内,这对应于超重和肥胖。 有些客户的BMI非常高(>40)。

就儿童数量而言,分布严重倾斜:大多数客户没有孩子。

从支出中可以看出,大多数人更愿意花费低于30k单位。

接下来,我们将分析一个或另一个功能如何相互依赖。 考虑特征的相关性。 我们将使用Spearman相关性,因为它不需要分布的正态性。

In [ ]:
spearman_corr = df_encode.corr(method='spearman')
spearman_corr['charges'].sort_values(ascending=False)
Out[0]:
charges             1.000000
smoker_yes          0.663460
age                 0.534392
children            0.133339
bmi                 0.119396
region_northeast    0.046109
region_southeast    0.017275
sex_male            0.009490
region_northwest   -0.021634
region_southwest   -0.042354
Name: charges, dtype: float64

根据上表,我们可以得出结论,吸烟和年龄因素与费用有很高的相关性,即如果一个人吸烟,那么由于需要花在香烟上,他有更多的费用。 同样随着年龄的增长。 根据年龄的不同,一个人有不同的费用。 您还可以了解到,儿童数量不会对支出产生很大影响(您可以看到大多数观察到的人没有孩子,因此与支出的相关性很小),以及居住地区,性别和bmi。

让我们看看分类功能如何影响支出。

In [ ]:
fig, axes = plt.subplots(1, 3, figsize=(18, 5))
sns.boxplot(y="region", x="charges", data=df, ax=axes[0])
axes[0].set_title("按地区划分的开支")

sns.boxplot(y="sex", x="charges", data=df, ax=axes[1])
axes[1].set_title("按性别划分的开支")

sns.boxplot(y="smoker", x="charges", data=df, ax=axes[2])
axes[2].set_title("吸烟费用")

plt.tight_layout()
plt.show()

从各地区可以看出,成本几乎没有差异。 地区分布相似,排放随处可见。

性别也没有明显的差异。

吸烟的情况是相反的。 对于非吸烟者来说,成本低于10千,而对于吸烟者来说-大约是35千。 吸烟者之间的传播也要广泛得多,这表明存在大量成本极高的病例。

In [ ]:
plt.figure(figsize=(8,5))
sns.scatterplot(x="age", y="charges", hue="smoker", data=df, alpha=0.6)
plt.title("费用对年龄的依赖,考虑到吸烟")
plt.xlabel("年龄")
plt.ylabel("开支 ")
plt.show()

您可以看到成本随着年龄的增长而线性增加,即为老年人提供服务更昂贵。 同样清楚的是,在吸烟因素存在的情况下,有一定的系数可以改变费用对年龄的依赖性。

In [ ]:
df['bmi_group'] = pd.cut(df['bmi'], bins=[0, 25, 30, 100],
                         labels=['标准', '超重', '肥胖'])
In [ ]:
g = sns.FacetGrid(df, col="smoker", hue="bmi_group", height=5)
g.map(sns.scatterplot, "age", "charges", alpha=0.6).add_legend()
plt.show()

从上面的图表可以得出以下结论:

对于非吸烟者,费用随着年龄的增长而增加,但通常保持在中等限度内。 即使肥胖,成本通常不超过20-25万。 这表明,如果没有吸烟因素,体重的影响就不那么明显。

吸烟者中有两条线清晰可见:一组吸烟者平均花费约20千人-体重正常或超重的人,另一组持续较高-约35-50千人-这些是肥胖吸烟者。

让我们检查孩子的数量是否影响人们的开支。

In [ ]:
plt.figure(figsize=(8,5))
sns.barplot(x="children", y="charges", data=df, estimator=lambda x: x.mean())
plt.title("按子女人数划分的平均开支")
plt.xlabel("儿童人数")
plt.ylabel("平均开支")
plt.show()

儿童人数对医疗费用几乎没有影响。 唯一的事情是当有2-3个孩子时,费用略高于平均水平

让我们来看看标志的相关性的全貌,画一张热图,如下图所示。

In [ ]:
sns.heatmap(df_encode.corr(method='spearman'), cmap="coolwarm", annot=False)
Out[0]:
<Axes: >

如果您通过标志查看相关性,您还可以看到bmi与居住区域,特别是"西南"区域之间存在轻微关系。 也就是说,它们具有更明显的线性关系。

模型训练

我们将在培训期间评估的模型列表

  1. 线性回归
  2. 随机森林
  3. CatBoost回归器

班轮回归(套索)

让我们创建一个表,我们将记录模型训练的结果。

In [ ]:
Result_model = pd.DataFrame(columns=["Model", "MAE", "RMSE", "R2"])

对于线性模型,正确使用类别变量非常重要,这就是为什么我们使用一个热编码对它们进行编码的原因。

在下面的代码中,y是我们的目标变量,成本,X是模型将被训练的特征。 整体数据集分为测试集和训练集,这样在训练模型之后,我们就可以对其尚未遇到的数据评估其泛化能力。

我们将用于评估训练模型的指标:

  1. MAE是平均绝对误差,它将显示模型平均错误的程度。
  2. RMSE是均方根误差,大的误差受到更严重的惩罚
  3. R2是确定系数,它显示了模型如何很好地解释"数据
In [ ]:
X = df_encode.drop("charges", axis=1)
y = df_encode["charges"]

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)

第一个模型将是套索。 下面的代码提供了模型认为信息较少的训练、度量计算和输出。 然而,即使这样的迹象可以有显着的贡献。

In [ ]:
lasso = Lasso(alpha=0.5)

lasso.fit(X_train, y_train)

y_pred_lasso = lasso.predict(X_test)

mae_lasso = mean_absolute_error(y_test, y_pred_lasso)
rmse_lasso = np.sqrt(mean_squared_error(y_test, y_pred_lasso))
r2_lasso = r2_score(y_test, y_pred_lasso)

print("Lasso Regression")
print("MAE:", mae_lasso)
print("RMSE:", rmse_lasso)
print("R²:", r2_lasso)

coef_lasso = pd.DataFrame({
    "Feature": X.columns,
    "Coefficient": lasso.coef_
}).sort_values(by="Coefficient", ascending=False)

print("套索系数:")
print(coef_lasso)
Lasso Regression
MAE: 3787.6327653920976
RMSE: 5729.913847940855
R²: 0.7797405299032881
Коэффициенты Lasso:
            Feature   Coefficient
8        smoker_yes  23345.644279
4  region_northeast    939.663756
5  region_northwest    579.789302
2          children    573.769881
1               bmi    369.569828
0               age    257.868504
7  region_southwest     -0.000000
3          sex_male   -167.733537
6  region_southeast   -478.089891

向表中添加度量值

In [ ]:
row = pd.DataFrame({
    "Model": ["Lasso"],
    "MAE": [mae_lasso],
    "RMSE": [rmse_lasso],
    "R2": [r2_lasso]
})

Result_model = pd.concat([Result_model, row])
/tmp/ipython-input-3838164166.py:8: FutureWarning: The behavior of DataFrame concatenation with empty or all-NA entries is deprecated. In a future version, this will no longer exclude empty or all-NA columns when determining the result dtypes. To retain the old behavior, exclude the relevant entries before the concat operation.
  Result_model = pd.concat([Result_model, row])

随机森林

下一个模型将是随机森林。 原理是一样的,我们训练,我们测试,我们看标志的重要性

In [ ]:
rf = RandomForestRegressor(
    n_estimators=100,
    max_depth=4,
    min_samples_split = 20,
    min_samples_leaf = 20
)

rf.fit(X_train, y_train)

y_pred_rf = rf.predict(X_test)

mae_rf = mean_absolute_error(y_test, y_pred_rf)
rmse_rf = np.sqrt(mean_squared_error(y_test, y_pred_rf))
r2_rf = r2_score(y_test, y_pred_rf)

print("Random Forest")
print("MAE:", mae_rf)
print("RMSE:", rmse_rf)
print("R²:", r2_rf)

importances = pd.DataFrame({
    "Feature": X.columns,
    "Importance": rf.feature_importances_
}).sort_values(by="Importance", ascending=False)

print("\迹象的重要性:")
print(importances)
Random Forest
MAE: 2393.117022714948
RMSE: 3944.832628669223
R²: 0.8956011850181776

Важность признаков:
            Feature  Importance
8        smoker_yes    0.700647
1               bmi    0.175183
0               age    0.115571
2          children    0.007219
4  region_northeast    0.000805
3          sex_male    0.000345
7  region_southwest    0.000077
6  region_southeast    0.000077
5  region_northwest    0.000076

向表中添加指标

In [ ]:
row = pd.DataFrame({
    "Model": ["Random Forest"],
    "MAE": [mae_rf],
    "RMSE": [rmse_rf],
    "R2": [r2_rf]
})

Result_model = pd.concat([Result_model, row])

可以看出,随机森林模型的度量比线性模型的度量要好得多。

CatBoost回归器

下一个模型是CatBosot包中的梯度提升模型。 让我们通过与上面的模型相同的管道。

In [ ]:
cat_features = ["smoker", "sex", "region"]
feature_cols = [c for c in df.columns if c not in ["charges"]]


cat = CatBoostRegressor(
    iterations=1000,
    depth=6,
    learning_rate=0.01,
    verbose=100, random_state=42
)

X = df[feature_cols]
y = df["charges"]

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)

cat.fit(X_train, y_train, cat_features=cat_features, eval_set=(X_test, y_test), verbose=100)

y_pred_cat = cat.predict(X_test)

mae_cat1 = mean_absolute_error(y_test, y_pred_cat)
rmse_cat1 = np.sqrt(mean_squared_error(y_test, y_pred_cat))
r2_cat1 = r2_score(y_test, y_pred_cat)

feature_importance = cat.get_feature_importance(prettified=True)
print(feature_importance)

print("CatBoost")
print("MAE:", mae_cat1)
print("RMSE:", rmse_cat1)
print("R²:", r2_cat1)
0:	learn: 12007.5544064	test: 12015.2822361	best: 12015.2822361 (0)	total: 1.01ms	remaining: 1.01s
100:	learn: 6642.7346665	test: 6462.9453476	best: 6462.9453476 (100)	total: 75.9ms	remaining: 675ms
200:	learn: 5028.4127086	test: 4811.8940123	best: 4811.8940123 (200)	total: 168ms	remaining: 670ms
300:	learn: 4569.0406421	test: 4403.8806511	best: 4403.8806511 (300)	total: 250ms	remaining: 582ms
400:	learn: 4392.6205165	test: 4306.4059699	best: 4306.4059699 (400)	total: 334ms	remaining: 499ms
500:	learn: 4299.2805777	test: 4278.5911022	best: 4278.5911022 (500)	total: 414ms	remaining: 412ms
600:	learn: 4229.7798849	test: 4268.2470356	best: 4268.2470356 (600)	total: 496ms	remaining: 330ms
700:	learn: 4174.5635367	test: 4266.5310336	best: 4265.5489448 (698)	total: 574ms	remaining: 245ms
800:	learn: 4115.5054728	test: 4266.3003965	best: 4265.2015983 (774)	total: 655ms	remaining: 163ms
900:	learn: 4065.5331955	test: 4270.9462529	best: 4265.2015983 (774)	total: 748ms	remaining: 82.2ms
999:	learn: 4012.0603582	test: 4275.5718995	best: 4265.2015983 (774)	total: 833ms	remaining: 0us

bestTest = 4265.201598
bestIteration = 774

Shrink model to first 775 iterations.
  Feature Id  Importances
0     smoker    74.294819
1        bmi    13.216792
2        age     9.892903
3   children     1.253336
4     region     0.901687
5        sex     0.440463
CatBoost
MAE: 2465.2188274260034
RMSE: 4265.2015982088515
R²: 0.8759281804317242

向表中添加指标

In [ ]:
row = pd.DataFrame({
    "Model": ["CatBoostRegressor 1ver"],
    "MAE": [mae_cat1],
    "RMSE": [rmse_cat1],
    "R2": [r2_cat1]
})

Result_model = pd.concat([Result_model, row])

接下来,我们将尝试为梯度提升模型选择更多最优超参数。 在这里,我上面描述的三个指标将同时考虑在内。

我们将通过随机选择模型使用的参数及其范围来搜索超参数。

In [ ]:
scoring = {
    "mae": "neg_mean_absolute_error",
    "rmse": "neg_root_mean_squared_error",
    "r2": "r2",
}

cat_base = CatBoostRegressor(
    verbose=False,
    random_state=42,
    task_type="GPU",
    devices="0"
)

param_distributions = {
    "iterations": randint(300, 1000),
    "depth": randint(4, 8),
    "learning_rate": loguniform(1e-3, 3e-1),
    "l2_leaf_reg": loguniform(1e-2, 1e2),
    "bagging_temperature": uniform(0.0, 1.0),
    "random_strength": loguniform(1e-3, 10),
    "grow_policy": ["SymmetricTree", "Depthwise"],
    "border_count": randint(32, 128),
    "leaf_estimation_iterations": randint(1, 3)
}

rs = RandomizedSearchCV(
    estimator=cat_base,
    param_distributions=param_distributions,
    n_iter=20,
    scoring=scoring,
    cv=KFold(n_splits=5, shuffle=True, random_state=42),
    n_jobs=1,
    random_state=42,
    refit=False
)


rs.fit(X_train, y_train, cat_features=cat_features)

接下来,我们将在所有指标中找到最佳结果。 在我们的例子中,确定系数通常显示所有指标的最佳数字。

In [ ]:
cv = rs.cv_results_
best_idx = np.argmax(cv["mean_test_r2"])
best_params = cv["params"][best_idx]
print("R2的最佳配置:", best_params)
Лучшая конфигурация по r2: {'bagging_temperature': np.float64(0.3562978380769749), 'border_count': 93, 'depth': 4, 'grow_policy': 'SymmetricTree', 'iterations': 676, 'l2_leaf_reg': np.float64(3.8972698898745795), 'leaf_estimation_iterations': 1, 'learning_rate': np.float64(0.10624824455988377), 'random_strength': np.float64(2.7728241828010627)}

使用获得的超参数,我们将训练模型

In [ ]:
best_cat = CatBoostRegressor(
    verbose=False, random_state=42, task_type="GPU", devices="0", **best_params
)
best_cat.fit(X_train, y_train, cat_features=cat_features)

y_pred = best_cat.predict(X_test)

mae  = mean_absolute_error(y_test, y_pred)
rmse = np.sqrt(mean_squared_error(y_test, y_pred))
r2   = r2_score(y_test, y_pred)

print("MAE:", mae)
print("RMSE:", rmse)
print("R²:", r2)

fi = best_cat.get_feature_importance(prettified=True)
print(fi)
MAE: 2349.3107371864035
RMSE: 4288.497881809796
R²: 0.8745691328646068
  Feature Id  Importances
0     smoker    72.454902
1        bmi    14.903237
2        age    10.728109
3   children     0.901835
4     region     0.835978
5        sex     0.175939

我们还将计算的指标添加到表中。

In [ ]:
row = pd.DataFrame({
    "Model": ["CatBoostRegressor 2ver"],
    "MAE": [mae],
    "RMSE": [rmse],
    "R2": [r2]
})

Result_model = pd.concat([Result_model, row])

让我们通过从现有功能中创建新功能来进行功能工程。 在我们的例子中,我们将年龄分为几十年,将有孩子的人分成小组,也将bmi指数分为类(即按间隔)。

In [ ]:
df = pd.read_csv("insurance.csv")
df["age_decade"] = (df["age"] // 10).astype(int).astype("category")
df["children_bucket"] = pd.cut(df["children"], [-1,0,2,99], labels=["0","1-2","3+"])
df["obesity_class"] = pd.cut(df["bmi"],
    [0,18.5,25,30,35,40,1e3], labels=["Under","Normal","Over","Ob1","Ob2", "Ob3"])

之后,我们将进行相同的管道来训练基本模型。

In [ ]:
cat_features = ["smoker", "sex", "region", "obesity_class", "age_decade", "children_bucket"]
feature_cols = [c for c in df.columns if c not in ["charges"]]


cat = CatBoostRegressor(
    iterations=2000,
    depth=6,
    learning_rate=0.01,
    verbose=100, random_state=42
)

X = df[feature_cols]
y = df["charges"]

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

cat.fit(X_train, y_train, cat_features=cat_features, eval_set=(X_test, y_test), verbose=100)

y_pred_cat = cat.predict(X_test)

mae_cat = mean_absolute_error(y_test, y_pred_cat)
rmse_cat = np.sqrt(mean_squared_error(y_test, y_pred_cat))
r2_cat = r2_score(y_test, y_pred_cat)

feature_importance = cat.get_feature_importance(prettified=True)
print(feature_importance)

print("CatBoost")
print("MAE:", mae_cat)
print("RMSE:", rmse_cat)
print("R²:", r2_cat)
0:	learn: 11933.3722839	test: 12382.0165070	best: 12382.0165070 (0)	total: 3.25ms	remaining: 6.5s
100:	learn: 6553.3062212	test: 6700.8384991	best: 6700.8384991 (100)	total: 199ms	remaining: 3.74s
200:	learn: 4995.8505835	test: 4931.1641999	best: 4931.1641999 (200)	total: 380ms	remaining: 3.4s
300:	learn: 4564.5766881	test: 4458.1505879	best: 4458.1505879 (300)	total: 558ms	remaining: 3.15s
400:	learn: 4412.3726510	test: 4335.9529290	best: 4335.9529290 (400)	total: 742ms	remaining: 2.96s
500:	learn: 4326.2994050	test: 4286.3989761	best: 4286.3989761 (500)	total: 925ms	remaining: 2.77s
600:	learn: 4250.9120302	test: 4263.9524398	best: 4263.9524398 (600)	total: 1.12s	remaining: 2.6s
700:	learn: 4183.8125620	test: 4250.3936707	best: 4250.3936707 (700)	total: 1.33s	remaining: 2.46s
800:	learn: 4112.8395328	test: 4241.2414234	best: 4241.2414234 (800)	total: 1.52s	remaining: 2.28s
900:	learn: 4045.4146292	test: 4234.0665711	best: 4233.8518279 (895)	total: 1.71s	remaining: 2.09s
1000:	learn: 3984.3708564	test: 4224.9261609	best: 4224.9201685 (999)	total: 1.91s	remaining: 1.9s
1100:	learn: 3930.0179700	test: 4219.6165314	best: 4219.5164715 (1098)	total: 2.11s	remaining: 1.72s
1200:	learn: 3874.3713973	test: 4211.8745119	best: 4211.6638182 (1196)	total: 2.33s	remaining: 1.55s
1300:	learn: 3827.2743082	test: 4206.4109026	best: 4206.2045928 (1295)	total: 2.55s	remaining: 1.37s
1400:	learn: 3780.7948401	test: 4204.2963017	best: 4204.2062397 (1397)	total: 2.75s	remaining: 1.18s
1500:	learn: 3736.6273160	test: 4200.9151489	best: 4200.2939566 (1472)	total: 2.95s	remaining: 981ms
1600:	learn: 3693.6442830	test: 4197.3577996	best: 4197.3577996 (1600)	total: 3.16s	remaining: 787ms
1700:	learn: 3656.7214768	test: 4196.6087530	best: 4196.2931068 (1667)	total: 3.39s	remaining: 595ms
1800:	learn: 3614.5572627	test: 4191.9198802	best: 4191.6668283 (1788)	total: 3.59s	remaining: 397ms
1900:	learn: 3577.5533875	test: 4192.1423002	best: 4190.3489371 (1880)	total: 3.79s	remaining: 198ms
1999:	learn: 3538.0569603	test: 4189.4415208	best: 4189.1639386 (1972)	total: 4s	remaining: 0us

bestTest = 4189.163939
bestIteration = 1972

Shrink model to first 1973 iterations.
        Feature Id  Importances
0           smoker    72.348387
1              bmi    12.427742
2              age     5.875907
3       age_decade     3.649860
4    obesity_class     1.693303
5           region     1.549990
6  children_bucket     1.130091
7         children     1.018538
8              sex     0.306182
CatBoost
MAE: 2282.9523492387857
RMSE: 4189.163939059238
R²: 0.8869614306043833

好吧,让我们在表中写下指标。

In [ ]:
row = pd.DataFrame({
    "Model": ["CatBoostRegressor 3ver"],
    "MAE": [mae_cat],
    "RMSE": [rmse_cat],
    "R2": [r2_cat]
})

Result_model = pd.concat([Result_model, row])

接下来,让我们检查通过随机搜索找到的具有最佳参数的模型如何在具有新特征的数据集上表现。

In [ ]:
cat_features = ["smoker", "sex", "region", "obesity_class", "age_decade", "children_bucket"]
feature_cols = [c for c in df.columns if c not in ["charges"]]


best_cat = CatBoostRegressor(
    verbose=False, random_state=42, task_type="GPU", devices="0", **best_params
)

X = df[feature_cols]
y = df["charges"]

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.15, random_state=42)

cat.fit(X_train, y_train, cat_features=cat_features, eval_set=(X_test, y_test), verbose=100)

y_pred_cat = cat.predict(X_test)

mae_cat = mean_absolute_error(y_test, y_pred_cat)
rmse_cat = np.sqrt(mean_squared_error(y_test, y_pred_cat))
r2_cat = r2_score(y_test, y_pred_cat)

feature_importance = cat.get_feature_importance(prettified=True)
print(feature_importance)

print("CatBoost")
print("MAE:", mae_cat)
print("RMSE:", rmse_cat)
print("R²:", r2_cat)
0:	learn: 11969.9835938	test: 12238.5188345	best: 12238.5188345 (0)	total: 3.93ms	remaining: 7.86s
100:	learn: 6517.5611660	test: 6334.9234598	best: 6334.9234598 (100)	total: 184ms	remaining: 3.46s
200:	learn: 4968.6564731	test: 4634.0821533	best: 4634.0821533 (200)	total: 370ms	remaining: 3.31s
300:	learn: 4530.7969731	test: 4215.6273929	best: 4215.6273929 (300)	total: 575ms	remaining: 3.25s
400:	learn: 4378.8264679	test: 4131.8317375	best: 4131.8317375 (400)	total: 780ms	remaining: 3.11s
500:	learn: 4291.5397532	test: 4103.8390627	best: 4103.8390627 (500)	total: 971ms	remaining: 2.9s
600:	learn: 4219.7399430	test: 4096.2305784	best: 4095.6900510 (581)	total: 1.17s	remaining: 2.73s
700:	learn: 4155.1394118	test: 4092.7798366	best: 4091.9668157 (688)	total: 1.39s	remaining: 2.57s
800:	learn: 4095.8480642	test: 4095.3161950	best: 4091.9668157 (688)	total: 1.61s	remaining: 2.4s
900:	learn: 4042.9257970	test: 4099.4890827	best: 4091.9668157 (688)	total: 1.83s	remaining: 2.23s
1000:	learn: 3990.8418157	test: 4108.1147734	best: 4091.9668157 (688)	total: 2.04s	remaining: 2.03s
1100:	learn: 3944.5296308	test: 4111.2769369	best: 4091.9668157 (688)	total: 2.25s	remaining: 1.84s
1200:	learn: 3905.1795316	test: 4116.7957579	best: 4091.9668157 (688)	total: 2.48s	remaining: 1.65s
1300:	learn: 3861.9687555	test: 4122.1770834	best: 4091.9668157 (688)	total: 2.69s	remaining: 1.45s
1400:	learn: 3821.8052445	test: 4129.8965665	best: 4091.9668157 (688)	total: 2.9s	remaining: 1.24s
1500:	learn: 3775.5377192	test: 4137.3658247	best: 4091.9668157 (688)	total: 3.11s	remaining: 1.03s
1600:	learn: 3732.5373524	test: 4147.7384160	best: 4091.9668157 (688)	total: 3.33s	remaining: 831ms
1700:	learn: 3685.3535428	test: 4156.7425063	best: 4091.9668157 (688)	total: 3.57s	remaining: 627ms
1800:	learn: 3638.3932733	test: 4168.5845339	best: 4091.9668157 (688)	total: 3.77s	remaining: 417ms
1900:	learn: 3599.5367201	test: 4174.4762325	best: 4091.9668157 (688)	total: 3.99s	remaining: 208ms
1999:	learn: 3561.8668688	test: 4183.1028730	best: 4091.9668157 (688)	total: 4.2s	remaining: 0us

bestTest = 4091.966816
bestIteration = 688

Shrink model to first 689 iterations.
        Feature Id  Importances
0           smoker    76.880263
1              bmi    12.396217
2              age     4.684975
3       age_decade     3.173683
4         children     0.866415
5    obesity_class     0.768850
6  children_bucket     0.572277
7           region     0.475337
8              sex     0.181983
CatBoost
MAE: 2373.116962536972
RMSE: 4091.966861290222
R²: 0.8900345281700345

像往常一样,我们将结果记录在一个表中。

In [ ]:
row = pd.DataFrame({
    "Model": ["CatBoostRegressor 4ver"],
    "MAE": [mae_cat],
    "RMSE": [rmse_cat],
    "R2": [r2_cat]
})

Result_model = pd.concat([Result_model, row])

让我们进行一个实验。 我们会找到异常值并删除它们。 让我们检查梯度助推器模型的行为。

在这种情况下,我们正在删除数据,这些数据可能会极大地扭曲模型的图片并破坏指标。

我们删除以下数据:
0.05是分位数—低于5%观测值的值。

0.95是分位数值,高于该分位数值发现5%的观测值。

In [ ]:
df = pd.read_csv("insurance.csv")

numeric_cols = df.select_dtypes(include=[np.number]).columns

low = df[numeric_cols].quantile(0.05)
high = df[numeric_cols].quantile(0.95)

df_no_outliers = df[~((df[numeric_cols] < low) | (df[numeric_cols] > high)).any(axis=1)]

我们将显示有关数据集的信息

In [ ]:
df_no_outliers.info()
<class 'pandas.core.frame.DataFrame'>
Index: 1021 entries, 0 to 1337
Data columns (total 7 columns):
 #   Column    Non-Null Count  Dtype  
---  ------    --------------  -----  
 0   age       1021 non-null   int64  
 1   sex       1021 non-null   object 
 2   bmi       1021 non-null   float64
 3   children  1021 non-null   int64  
 4   smoker    1021 non-null   object 
 5   region    1021 non-null   object 
 6   charges   1021 non-null   float64
dtypes: float64(2), int64(2), object(3)
memory usage: 63.8+ KB

可以看出,大约有300份数据被删除。 在这种情况下,我们失去了所有信息的20%,这是不推荐的,但我们看看这20%如何影响模型的最终预测。

让我们构建在上面段落中构建的箱线图,并估计数据的分布。

In [ ]:
fig, axes = plt.subplots(1, 3, figsize=(18, 5))
sns.boxplot(y="region", x="charges", data=df_no_outliers, ax=axes[0])
axes[0].set_title("按地区划分的开支")

sns.boxplot(y="sex", x="charges", data=df_no_outliers, ax=axes[1])
axes[1].set_title("按性别划分的开支")

sns.boxplot(y="smoker", x="charges", data=df_no_outliers, ax=axes[2])
axes[2].set_title("吸烟费用")

plt.tight_layout()
plt.show()

可见排放量略少

接下来,我们将在裁剪的数据集上训练梯度提升模型,并检查度量

In [ ]:
cat_features = ["smoker", "sex", "region"]
feature_cols = [c for c in df.columns if c not in ["charges"]]


cat = CatBoostRegressor(
    iterations=2700,
    depth=9,
    learning_rate=0.002,
    verbose=100, random_state=42
)

X = df_no_outliers[feature_cols]
y = df_no_outliers["charges"]

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.15, random_state=42)

cat.fit(X_train, y_train, cat_features=cat_features, eval_set=(X_test, y_test), verbose=100)

y_pred_cat = cat.predict(X_test)

mae_cat = mean_absolute_error(y_test, y_pred_cat)
rmse_cat = np.sqrt(mean_squared_error(y_test, y_pred_cat))
r2_cat = r2_score(y_test, y_pred_cat)

feature_importance = cat.get_feature_importance(prettified=True)
print(feature_importance)

print("CatBoost")
print("MAE:", mae_cat)
print("RMSE:", rmse_cat)
print("R²:", r2_cat)
0:	learn: 9897.0413125	test: 8906.4267767	best: 8906.4267767 (0)	total: 851us	remaining: 2.3s
100:	learn: 8792.4839207	test: 7812.1211522	best: 7812.1211522 (100)	total: 104ms	remaining: 2.68s
200:	learn: 7891.9644000	test: 6905.9187320	best: 6905.9187320 (200)	total: 220ms	remaining: 2.74s
300:	learn: 7176.7820512	test: 6169.8327394	best: 6169.8327394 (300)	total: 340ms	remaining: 2.71s
400:	learn: 6611.0879826	test: 5576.5673997	best: 5576.5673997 (400)	total: 465ms	remaining: 2.67s
500:	learn: 6164.4200911	test: 5094.1977204	best: 5094.1977204 (500)	total: 614ms	remaining: 2.7s
600:	learn: 5815.7735646	test: 4705.3250723	best: 4705.3250723 (600)	total: 753ms	remaining: 2.63s
700:	learn: 5547.3455048	test: 4402.8985985	best: 4402.8985985 (700)	total: 917ms	remaining: 2.62s
800:	learn: 5339.6025349	test: 4159.2304067	best: 4159.2304067 (800)	total: 1.06s	remaining: 2.52s
900:	learn: 5169.1714018	test: 3959.3475285	best: 3959.3475285 (900)	total: 1.21s	remaining: 2.42s
1000:	learn: 5035.4947364	test: 3810.6290259	best: 3810.6290259 (1000)	total: 1.38s	remaining: 2.34s
1100:	learn: 4924.9501339	test: 3687.2531723	best: 3687.2531723 (1100)	total: 1.54s	remaining: 2.24s
1200:	learn: 4835.3711923	test: 3592.3664282	best: 3592.3664282 (1200)	total: 1.72s	remaining: 2.14s
1300:	learn: 4759.9560259	test: 3516.2057424	best: 3516.2057424 (1300)	total: 1.93s	remaining: 2.07s
1400:	learn: 4692.2595470	test: 3453.3606403	best: 3453.3606403 (1400)	total: 2.12s	remaining: 1.96s
1500:	learn: 4634.5055861	test: 3407.5697656	best: 3407.5697656 (1500)	total: 2.31s	remaining: 1.85s
1600:	learn: 4582.9786436	test: 3371.3128485	best: 3371.3128485 (1600)	total: 2.52s	remaining: 1.73s
1700:	learn: 4539.4351243	test: 3343.9115287	best: 3343.8764532 (1699)	total: 2.7s	remaining: 1.59s
1800:	learn: 4492.5670431	test: 3326.1500465	best: 3326.1500465 (1800)	total: 2.93s	remaining: 1.46s
1900:	learn: 4449.1709672	test: 3312.8196046	best: 3312.6574863 (1899)	total: 3.17s	remaining: 1.33s
2000:	learn: 4414.4671137	test: 3300.9165097	best: 3300.9165097 (2000)	total: 3.36s	remaining: 1.17s
2100:	learn: 4377.7072952	test: 3291.6828146	best: 3291.5949542 (2099)	total: 3.58s	remaining: 1.02s
2200:	learn: 4347.6894171	test: 3284.1062182	best: 3284.0668611 (2197)	total: 3.78s	remaining: 858ms
2300:	learn: 4318.7481154	test: 3280.6572629	best: 3280.2862776 (2286)	total: 4.02s	remaining: 697ms
2400:	learn: 4288.9738170	test: 3277.1930535	best: 3277.1316001 (2394)	total: 4.24s	remaining: 528ms
2500:	learn: 4256.6900365	test: 3274.7293469	best: 3274.0859575 (2482)	total: 4.47s	remaining: 356ms
2600:	learn: 4224.1697094	test: 3274.7086500	best: 3274.0859575 (2482)	total: 4.7s	remaining: 179ms
2699:	learn: 4197.7877597	test: 3274.8881425	best: 3274.0859575 (2482)	total: 4.92s	remaining: 0us

bestTest = 3274.085958
bestIteration = 2482

Shrink model to first 2483 iterations.
  Feature Id  Importances
0     smoker    71.994475
1        age    11.329495
2        bmi    11.115244
3   children     2.485361
4     region     2.124998
5        sex     0.950427
CatBoost
MAE: 2296.9732261320055
RMSE: 3274.0859712161337
R²: 0.8636492205195162

让我们把所有的东西都放在平板电脑上。

In [ ]:
row = pd.DataFrame({
    "Model": ["CatBoostRegressor 5ver"],
    "MAE": [mae_cat],
    "RMSE": [rmse_cat],
    "R2": [r2_cat]
})

Result_model = pd.concat([Result_model, row], ignore_index=True)

让我们评估结果

In [ ]:
Result_model
Out[0]:
Model MAE RMSE R2
0 Lasso 3787.632765 5729.913848 0.779741
1 Random Forest 2393.117023 3944.832629 0.895601
2 CatBoostRegressor 1ver 2465.218827 4265.201598 0.875928
3 CatBoostRegressor 2ver 2349.310737 4288.497882 0.874569
4 CatBoostRegressor 3ver 2282.952349 4189.163939 0.886961
5 CatBoostRegressor 4ver 2373.116963 4091.966861 0.890035
6 CatBoostRegressor 5ver 2296.973226 3274.085971 0.863649

从下表可以看出,R2指标的最佳值由随机森林和梯度提升模型(版本5)显示。 但是,应该记住,梯度提升模型是在变换数据上训练的,其中删除了潜在的异常值(即使这样的客户端实际上可能发生在样本中)。 与此同时,随机森林模型在初始数据集上显示了更高的R2值,但其RMSE和MAE指标更差。 这表明在初始数据中存在成本显着高于平均水平的观测值。 因此,平均价值约为20千单位,有些情况下费用约为60千单位。 这样的极值增加了随机森林的绝对度量中的误差。 相比之下,没有异常值训练的梯度提升模型显示出较低的MAE和RMSE误差。

因此,在所有训练好的模型中,我们将认为指标最低的模型是最好的。

在我们的例子中,这是梯度提升版本5。

哪些因素对目标影响最大?

让我们用2辐条来评估标志的重要性:

  1. 基于EDA的重要性
  2. 在模型的帮助下获得的重要性

基于EDA的重要性

让我们分别考虑所有的迹象

吸烟(吸烟者)

最显着的因素。 吸烟与费用之间的相关系数为0.66,这表明强烈的关系。

图表显示,吸烟者的支出明显高于非吸烟者。

散点图还显示,即使年龄和BMI相同,吸烟者的成本也会显着提高。
吸烟极大地增加了健康风险(心脏病,肺病,癌症),从而导致更高的成本。

年龄(age)

年龄在影响力方面排在第二位(相关性0.53)。

图表显示,一个人年龄越大,成本就越高。

这对于吸烟者来说尤其明显:随着年龄的增长,他们的费用增加,看起来有点像指数依赖。
原因是,随着年龄的增长,患慢性病和寻求医疗帮助的可能性增加。

儿童的存在

相关性较弱(0.13),但仍然为正。

有2-3个孩子的家庭平均支出高于没有孩子或有一个孩子的家庭。

然而,对于5个孩子,平均费用减少—这可能是由于样本的特征。
原因是生孩子会增加健康成本

体重指数(BMI)

与费用的相关性较弱(0.11),但与其他因素相结合,BMI发挥作用。

图表显示,肥胖的人有更高的费用,特别是如果他们吸烟。

与此同时,不吸烟的肥胖并不总是导致成本急剧增加。
原因是肥胖与糖尿病和心血管疾病的风险有关,但这些影响因其他风险因素而增强。

性别与地区(sex,region)

性别或地区没有明显的影响。

相关性接近于零。

男性和女性以及不同地区的费用分配几乎相同。
原因是保险费率和药品不依赖于该样本中的居住地区或性别。

模型的帮助下获得的重要性

如果我们在训练模型后查看哪些特征更多,哪些权重更小,我们将看到类似的重要性图片。

因此,例如,对于随机森林模型,训练后特征的重要性系数:

  1.           烟鬼0.700647
    
  2.           体重指数0.175183
    
  3.           年龄0.115571
    

可以看出,在EDA期间也发现这些非常标志具有很高的重要性。

例如,对于两个梯度增强模型,这些特征是重要的,只是具有不同的系数。:

  1.    吸烟者    
    
  2.    体重指数   
    
  3.    年龄    
    

因此,可以明确得出结论,最显着的迹象是吸烟因素,bmi指数和年龄。

结论

在该示例中,执行了探索性数据分析,训练了回归模型,并确定了影响目标变量的最显着特征。